package com.swipemaster.levels;

import android.graphics.Canvas;

import com.swipemaster.common.Const;
import com.swipemaster.elements.Circle;
import com.swipemaster.elements.Element;
import com.swipemaster.elements.Particle;
import com.swipemaster.game.ClockTime;
import com.swipemaster.game.ThreadManager;
import com.swipemaster.moves.FreeMove;

/** Generic level class. We should derive from this class. */
abstract public class Level
{
	/** Seconds you have to survive to pass. If 0, typically on puzzle levels, then ignore this constraint. */
	public double timeToWin;
	/** Seconds to get stars on certain level. On puzzle levels under this time you get stars, on survivals - above. */
	public double timeFor1Star, timeFor2Stars, timeFor3Stars;
	/** Element steered by player. */
	public Circle player;
	/** Enemies' elements. */
	protected final Element[] enemies = new Element[Const.MAX_ENEMIES];
	/** Number of enemies on this level. */
	protected int enemiesCounter = 0;
	/** Are free moving elements bouncing with each other. */
	protected boolean bounce = false;
	/** Table to mark which of free moving elements we have already bounced. We can't bounce them twice or more. */
	protected final boolean alreadyBounced[][] = new boolean[Const.MAX_ENEMIES][Const.MAX_ENEMIES];
	/** If particles are on. */
	protected boolean particlesActive = true;
	/** Maximal number of particles at one moment on scene. */
	protected static final int MAX_PARTICLES = 20;
	/** Maximal number of particles in single collision */
	protected static final int MAX_PARTICLES_AT_ONCE = 4;
	/** place in array of particles, from where move can resurrect particles (longest unused). */
	protected int firstAvailableParticle = 0;
	/** Particles table, preallocated. */
	protected final Particle particles[] = new Particle[MAX_PARTICLES];
	/** Clock to measure playing time. */
	protected final ClockTime clock = new ClockTime();

	/** Moments when elements speed up a little. */
	protected int speedUpTimes[];
	protected int speedUpTimeIndex;
	protected boolean speedUp;
	protected double speedUpScale = Const.UNIVERSAL_SPEED_UP;

	public Level(Circle player)
	{
		this.player = player;
		for (int i = 0; i < particles.length; i++)
		{
			particles[i] = new Particle();
		}
	}

	public Level(Circle player, double timeToWin, double timeFor1Star, double timeFor2Stars, double timeFor3Stars)
	{
		this.player = player;
		this.timeToWin = timeToWin;
		this.timeFor1Star = timeFor1Star;
		this.timeFor2Stars = timeFor2Stars;
		this.timeFor3Stars = timeFor3Stars;
		for (int i = 0; i < particles.length; i++)
		{
			particles[i] = new Particle();
		}
	}

	/** Adds an enemy element to the level. */
	public void addEnemy(Element enemy)
	{
		if (enemiesCounter >= Const.MAX_ENEMIES)
		{
			throw new IllegalArgumentException("Exceeded maximal number of enemies on level");
		}
		enemies[enemiesCounter++] = enemy;
	}

	/** Bouncing free moving elements. */
	public void bounceElements()
	{
		for (int i = 0; i < enemiesCounter; i++)
		{
			for (int j = i + 1; j < enemiesCounter; j++)
			{
				if (enemies[i].isCollisionWith(enemies[j]))
				{
					if (!alreadyBounced[i][j])
					{
						enemies[i].bounceWith(enemies[j]);
						alreadyBounced[i][j] = true;
					}
					continue;
				}
				else
				{
					alreadyBounced[i][j] = false;
				}
			}
		}
	}

	/** Sets particles to be active at given coordinates and exploding in given direction. */
	public void computeParticles(int x, int y, int direction, int howMany)
	{
		if (howMany > MAX_PARTICLES_AT_ONCE)
		{
			howMany = MAX_PARTICLES_AT_ONCE;
		}
		final int upTo = (howMany + firstAvailableParticle) % MAX_PARTICLES;

		for (int i = firstAvailableParticle; i != upTo; i++, i %= MAX_PARTICLES)
		{
			particles[i].compute(x, y, direction);
		}
		firstAvailableParticle += howMany;
		firstAvailableParticle %= MAX_PARTICLES;
	}

	/** Draw everything. Subclasses should call this method with super.draw(canvas). Order of drawing is important, first
	 * drawings can be overridden by the latter ones. */
	public void draw(Canvas canvas)
	{
		player.draw(canvas);
		if (enemies == null)
		{
			return;
		}
		for (int i = 0; i < enemiesCounter; i++)
		{
			enemies[i].draw(canvas);
		}
	}

	public double getPlayingTime()
	{
		return clock.getPlayingTime();
	}

	/** Should be called after onLevelEnd().
	 * @return Number of stars which player gains for his last game. */
	abstract public int getStars();

	/** @return True, if user is already a loser. */
	abstract public boolean isLoser();

	/** @return True, if user is already a winner. */
	public abstract boolean isWinner();

	/** Periodically called to move all the enemies. */
	public void moveEnemies()
	{
		final int interval = (int) clock.addPlayingTime();

		for (int i = 0; i < enemiesCounter; i++)
		{
			enemies[i].move(interval);
		}

		if (bounce)
		{
			bounceElements();
		}

		if (speedUp)
		{
			speedUpElements(speedUpScale);
		}

		if (particlesActive)
		{
			for (final Particle particle : particles)
			{
				particle.updatePosition(interval);
			}
		}
	}

	/** @param bouncing Should free moving elements collide with each other? */
	public void setBouncing(boolean bouncing)
	{
		bounce = bouncing;
	}

	/** @param particles Draw particles when collision occurs? */
	public void setParticlesActive(boolean particles)
	{
		particlesActive = particles;
		for (int i = 0; i < enemiesCounter; i++)
		{
			if (enemies[i].move instanceof FreeMove)
			{
				final FreeMove freeMove = (FreeMove) enemies[i].move;
				freeMove.setParticlesActive(particlesActive);
			}
		}
	}

	/** @param isRunning Whether the level is running or not. */
	public void setRunning(boolean isRunning)
	{
		if (isRunning)
		{
			clock.reset();
		}

		ThreadManager.setRunning(isRunning);
	}

	public void setSpeedUpTimes(int times[])
	{
		speedUpTimes = times;
		speedUp = true;
	}

	public void setSpeedUpTimes(int times[], double scale)
	{
		setSpeedUpTimes(times);
		speedUpScale = scale;
	}

	/** @return True, if player is colliding with any enemy. */
	protected boolean isPlayerCollidingWithEnemy()
	{
		if (enemies == null)
		{
			return false;
		}
		for (int i = 0; i < enemiesCounter; i++)
		{
			if (enemies[i].isCollisionWith(player))
			{
				return true;
			}
		}
		return false;
	}

	protected void speedUpElements(double scale)
	{
		if (clock.getPlayingTime() > speedUpTimes[speedUpTimeIndex])
		{
			for (int i = 0; i < enemiesCounter; i++)
			{
				enemies[i].speedUp(scale);
			}

			if (++speedUpTimeIndex >= speedUpTimes.length)
			{
				speedUp = false;
			}
		}
	}
}
